package com.tourscool.springboot.starter.redisson;

import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RMapCache;
import org.redisson.api.RMultimapCache;
import org.redisson.api.RedissonClient;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 多Key缓存的Map数据结构。
 * 不同的Key对应到同一个Value。
 * 和普通Map不同的是Key存在很多个，可以多个不同的key存取数据。
 *
 * @author Carlton
 * @date 2018/3/5
 */
public class MultiCacheMap<O> {
    /**
     * 保存所有的Key。
     */
    private RMultimapCache<String, String> mKeysToKey;
    /**
     * 让所有的Key有一个对应组，这个组的key就是KeysToKey的值，和KeysToKey相反。
     */
    private RMultimapCache<String, String> mKeyGroup;
    /**
     * 保存具体的值
     */
    private RMapCache<String, O> mMap;
    private RedissonClient mRedissonClient;
    /**
     * Map名称
     */
    private String mName;

    public MultiCacheMap(String name, RedissonClient redissonClient) {
        mRedissonClient = redissonClient;
        mName = name;
        mKeysToKey = mRedissonClient.getListMultimapCache(name.toUpperCase() + ".MULTI_CACHE_KEYS_TO_KEY_MAP");
        mKeyGroup = mRedissonClient.getListMultimapCache(name.toUpperCase() + ".MULTI_CACHE_KEYS_GROUP");
        mMap = mRedissonClient.getMapCache(name.toUpperCase() + ".MULTI_CACHE_MAP");
    }

    /**
     * 获取所有的Key，可能两个Key对应的value是一样的。
     *
     * @return 所有的Key
     */
    public Set<String> keys() {
        return mKeysToKey.keySet();
    }

    /**
     * 值的Key，一个Key对应一个value。
     * 没有直接返回
     *
     * @return value对应的Key
     */
    public String[] valueKeys() {
        Set<String> keySet = mMap.keySet();
        return keySet.toArray(new String[keySet.size()]);
    }

    /**
     * 保存数据。线程同步的。在有效期时间后过期
     * 多个Key对应到一个Map上。
     * <pre>
     * Key1->|
     *       | bindKey -> value
     * Key2->|
     * </pre>
     *
     * @param keys    通过这里面的
     * @param bindKey 绑定的Map的Key。多个key对应到真实Map时候的Key。
     * @param value   Map的值
     * @param ttl   存活时间
     * @param unit   时间单位
     * @throws RuntimeException 异常
     */
    private void put(Set<String> keys, String bindKey, O value, Long ttl, TimeUnit unit) throws RuntimeException {
        if (StringUtils.isEmpty(bindKey) || value == null) {
            return;
        }
        RLock cacheLock = getLock();
        cacheLock.lock();
        boolean isKeysToKeyPut = false;
        try {
            for (String next : keys) {
                if (StringUtils.isEmpty(next) || mKeysToKey.containsEntry(next, bindKey)) {
                    continue;
                }
                bindKeys(next, bindKey, ttl, unit);
                isKeysToKeyPut = true;
            }
            if(null == ttl || null == unit){
                mMap.put(bindKey, value);
            }else {
                mMap.put(bindKey, value, ttl, unit);
            }

        } catch (RuntimeException e) {
            if (isKeysToKeyPut) {
                mKeysToKey.fastRemove(keys.toArray(new String[keys.size()]));
            }
            throw e;
        } finally {
            cacheLock.unlock();
        }
    }

    /**
     * 清空Map
     */
    public void clear() {
        mKeysToKey.clear();
        mKeyGroup.clear();
        mMap.clear();
    }

    /**
     * 移除单个key，仅仅移除对这个Key的引用，本身的数据还是会存在。
     * 比如：key1、key2。移除key1后，还可以通过key2来获取值。
     * 如果只有一个key的情况下，会连同值一起删除。
     *
     * @param key 需要删除的key
     */
    public void removeKey(String key) {
        final RLock lock = getLock();
        lock.lock();
        try {
            final Collection<String> strings = mKeysToKey.get(key);
            final String next = strings.iterator().next();
            mKeysToKey.fastRemove(key);
            mKeyGroup.remove(next, key);
            final Collection<String> keyGroup = mKeyGroup.get(next);
            if (keyGroup.size() < 1) {
                mMap.remove(next);
            }
        } finally {
            lock.unlock();
        }

    }

    /**
     * 移除Key对应的数据。
     * 这个方法会移除数据。
     * 比如：有key1、key2同时对应数据A。
     * 使用这个方法删除key1的时候，不但会删除key1、key2，还会把数据一起删除。
     *
     * @param key 需要删除数据的任意一个key
     */
    public void remove(String key) {
        final RLock lock = getLock();
        lock.lock();
        try {
            final Collection<String> strings = mKeysToKey.get(key);
            if (strings.isEmpty()) {
                return;
            }
            final String value = strings.iterator().next();
            if (strings.size() > 0) {
                final Collection<String> keyGroup = mKeyGroup.get(value);
                mKeysToKey.fastRemove(keyGroup.toArray(new String[keyGroup.size()]));
                mKeyGroup.fastRemove(value);
                mMap.fastRemove(value);
            }
        } finally {
            lock.unlock();
        }
    }

    private RLock getLock() {
        return mRedissonClient.getLock(mName.toUpperCase() + ".MULTI_CACHE_LOCK");
    }

    /**
     * 获取列表内容，一个key可能能够查出来很多个值。
     *
     * @param key MapKey
     * @return List对象
     */
    public List<O> get(String key) {
        Collection<String> keys = mKeysToKey.get(key);
        ArrayList<O> results = new ArrayList<>();
        if (keys.isEmpty()) {
            return results;
        }
        for (String k : keys) {
            results.add(mMap.get(k));
        }
        return results;
    }

    /**
     * 保存数据
     *
     * @param keys  key集合，同一个对象可以通过几个Key来取
     * @param value map的值
     * @throws RuntimeException 保存的时候会出现一些不可预知的运行时异常
     */
    public void put(Set<String> keys, O value) throws RuntimeException {
        for (String key : keys) {
            if (mKeysToKey.containsKey(key)) {
                // 如果已经有这个数据了，则更新
                Collection<String> strings = mKeysToKey.get(key);
                put(keys, strings.iterator().next(), value, null, null);
                return;
            }
        }
        put(keys, UUID.randomUUID().toString(), value, null, null);
    }

    /**
     * 保存数据并设置过期时间
     *
     * @param keys  key集合，同一个对象可以通过几个Key来取
     * @param value map的值
     * @throws RuntimeException 保存的时候会出现一些不可预知的运行时异常
     */
    public void put(Set<String> keys, O value, Long ttl, TimeUnit unit) throws RuntimeException {
        for (String key : keys) {
            if (mKeysToKey.containsKey(key)) {
                // 如果已经有这个数据了，则更新
                Collection<String> strings = mKeysToKey.get(key);
                put(keys, strings.iterator().next(), value, ttl, unit);
                return;
            }
        }
        put(keys, UUID.randomUUID().toString(), value, ttl, unit);
    }

    /**
     * 为数据添加一个新的Key并设置过期时间
     *
     * @param key    老的Key
     * @param newKey 新Key
     * @throws RuntimeException 异常
     */
    public void addKey(String key, String newKey, Long ttl, TimeUnit unit) throws RuntimeException {
        Collection<String> strings = mKeysToKey.get(key);
        if (strings.isEmpty()) {
            return;
        }

        String next = strings.iterator().next();
        if (mKeysToKey.containsEntry(newKey, next)) {
            return;
        }
        bindKeys(newKey, next, ttl, unit);
    }

    /**
     * 为数据添加一个新的Key
     *
     * @param key    老的Key
     * @param newKey 新Key
     * @throws RuntimeException 异常
     */
    public void addKey(String key, String newKey) throws RuntimeException {
        Collection<String> strings = mKeysToKey.get(key);
        if (strings.isEmpty()) {
            return;
        }

        String next = strings.iterator().next();
        if (mKeysToKey.containsEntry(newKey, next)) {
            return;
        }
        bindKeys(newKey, next, null, null);
    }

    private void bindKeys(String key, String keyToKey, Long ttl, TimeUnit unit) {
        mKeysToKey.put(key, keyToKey);
        mKeyGroup.put(keyToKey, key);
        if(null != ttl && null != unit){
            mKeysToKey.expireKey(key, ttl, unit);
            mKeyGroup.expireKey(keyToKey, ttl, unit);
        }
    }
}
